'Copyright (c) 2008, Jonathan Allen

Imports System.Runtime.CompilerServices
Imports ClrExtensions.Security.OAuth
Imports ClrExtensions.Net.Rest

Public Module NetflixUrls
	Private ReadOnly BaseUrl As String = "http://api.netflix.com/"

	Public Function GetAccessToken() As RestCall
		Return New RestCall(RestVerb.Get, BaseUrl & "oauth/access_token")
	End Function

	Public Function GetRequestToken() As RestCall
		Return New RestCall(RestVerb.Get, BaseUrl & "oauth/request_token")
	End Function

	Public Function PermissionLogin(ByVal connection As NetflixConnection, ByVal requestToken As RequestToken) As RestCall
		Return New RestCall(RestVerb.Get, "https://api-user.netflix.com/oauth/login?" & requestToken.RawData & "&oauth_consumer_key=" & connection.ConsumerKey)
	End Function

	Public Function GetCatalogTitles(ByVal term As String, ByVal startIndex As Integer?, ByVal maxResults As Integer?) As RestCall
		Return New RestCall(RestVerb.Get, BaseUrl & "catalog/titles?".AddTerm("term", term).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults))
    End Function

    Public Function GetCatalogTitles(ByVal term As String, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal expansions As TitleExpansions) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "catalog/titles?".AddTerm("term", term).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm(expansions.ToQueryParameter().Name, expansions.ToQueryParameter().Value))
    End Function

    Public Function GetCatalogTitlesAutocomplete(ByVal term As String) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "catalog/titles/autocomplete?".AddTerm("term", term))
    End Function

    Public Function GetCatalogTitlesIndex() As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "catalog/titles/index?")
    End Function


    'According to the API, we should not be trying to generate these directly
    'Public Function CatalogTitlesMovies(ByVal movieId As String) As RestCall
    '	Return BaseUrl & "catalog/titles/movies/" & movieId & "?"
    'End Function

    'Public Function CatalogTitlesSeries(ByVal seriesId As String) As RestCall
    '	Return BaseUrl & "catalog/titles/series/" & seriesId & "?"
    'End Function

    'Public Function CatalogTitlesSeriesSeasons(ByVal seriesId As String) As RestCall
    '	Return BaseUrl & "catalog/titles/series/" & seriesId & "/seasons?"
    'End Function

    'Public Function CatalogTitlesPrograms(ByVal programId As String) As RestCall
    '	Return BaseUrl & "catalog/titles/series/" & programId & "?"
    'End Function

    'Public Function CatalogTitlesDiscs(ByVal discId As String) As RestCall
    '	Return BaseUrl & "catalog/titles/series/" & discId & "?"
    'End Function

    Public Function GetSimilars(ByVal titleId As String, ByVal startIndex As Integer?, ByVal maxResults As Integer?) As RestCall
        Return New RestCall(RestVerb.Get, titleId & "/similars?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults))
    End Function

    Public Function GetCatalogPeople(ByVal term As String, ByVal startIndex As Integer?, ByVal maxResults As Integer?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "catalog/people?".AddTerm("term", term).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults))
    End Function

    Public Function GetUsers(ByVal accessToken As IAccessToken) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "?")
    End Function

    Public Function GetUsersFeeds(ByVal accessToken As IAccessToken) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/feeds?")
    End Function

    Public Function GetUsersTitleStates(ByVal accessToken As IAccessToken, ByVal titleRefs As String, ByVal ParamArray additionalTitleRefs As String()) As RestCall
        Dim result = BaseUrl & "users/" & accessToken.UserId & "/title_states?title_refs=" & UrlEncode(titleRefs)
        For Each item In additionalTitleRefs
            result &= "," & UrlEncode(item)
        Next
        Return New RestCall(RestVerb.Get, result)
    End Function

#If Full35 Then

    Public Function GetUsersQueues(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesDisc(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/disc?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesDiscAvailable(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/disc/available?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesDiscSaved(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/disc/saved?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesInstant(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/instant?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesInstantAvailable(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/instant/available?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesInstantSaved(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/instant/saved?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersAtHome(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/at_home?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersRentalHistory(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/rental_history?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersRentalHistoryShipped(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/rental_history/shipped?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersRentalHistoryReturned(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/rental_history/returned?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersRentalHistoryWatched(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/rental_history/watched?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

#End If

    Public Function GetUsersQueues(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesDisc(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/disc?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesDiscAvailable(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/disc/available?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    'Public Function DeleteUsersQueuesDiscAvailable(ByVal accessToken As IAccessToken, ByVal entryId As String) As RestCall
    '    Return New RestCall(RestVerb.Delete, BaseUrl & "users/" & accessToken.UserId & "/queues/disc/available/" & entryId)
    'End Function

    'Public Function DeleteUsersQueuesDiscSaved(ByVal accessToken As IAccessToken, ByVal entryId As String) As RestCall
    '    Return New RestCall(RestVerb.Delete, BaseUrl & "users/" & accessToken.UserId & "/queues/disc/saved" & entryId)
    'End Function

    'Public Function DeleteUsersQueuesInstantAvailable(ByVal accessToken As IAccessToken, ByVal entryId As String) As RestCall
    '    Return New RestCall(RestVerb.Delete, BaseUrl & "users/" & accessToken.UserId & "/queues/instant/available/" & entryId)
    'End Function

    'Public Function DeleteUsersQueuesInstantSaved(ByVal accessToken As IAccessToken, ByVal entryId As String) As RestCall
    '    Return New RestCall(RestVerb.Delete, BaseUrl & "users/" & accessToken.UserId & "/queues/instant/saved" & entryId)
    'End Function

    Public Function GetUsersQueuesDiscSaved(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/disc/saved?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesInstant(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/instant?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesInstantAvailable(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/instant/available?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersQueuesInstantSaved(ByVal accessToken As IAccessToken, ByVal sort As QueueSortOrder?, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/queues/instant/saved?".AddTerm("sort", sort.SortString).AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function PostUsersQueuesDisc(ByVal accessToken As IAccessToken, ByVal titleRef As String, ByVal format As DiscFormat?, ByVal position As Integer?, ByVal eTag As String) As RestCall
        Return New RestCall(RestVerb.Post, BaseUrl & "users/" & accessToken.UserId & "/queues/disc?".AddTerm("title_ref", titleRef).AddTerm("format", format.FormatString).AddTerm("position", position).AddTerm("etag", eTag))
    End Function

    Public Function PostUsersQueuesInstant(ByVal accessToken As IAccessToken, ByVal titleRef As String, ByVal format As DiscFormat?, ByVal position As Integer?, ByVal eTag As String) As RestCall
        Return New RestCall(RestVerb.Post, BaseUrl & "users/" & accessToken.UserId & "/queues/instant?".AddTerm("title_ref", titleRef).AddTerm("format", format.FormatString).AddTerm("position", position).AddTerm("etag", eTag))
    End Function

    Public Function GetUsersAtHome(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/at_home?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersRentalHistory(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/rental_history?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersRentalHistoryShipped(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/rental_history/shipped?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersRentalHistoryReturned(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/rental_history/returned?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersRentalHistoryWatched(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTime?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/rental_history/watched?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    End Function

    Public Function GetUsersRecommendations(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/recommendations?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults))
    End Function

    Public Function GetUsersRecommendations(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal expansions As TitleExpansions) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/recommendations?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm(expansions.ToQueryParameter().Name, expansions.ToQueryParameter().Value))
    End Function

    ''' <summary>
    ''' Formats a RestCall query that can be used to retrieve the actual Rating(s) assigned to the Title or Titles queried.
    ''' </summary>
    ''' <param name="accessToken">
    ''' The required access token, acquired in a previous RESTful request.
    ''' </param>
    ''' <param name="titleRef">
    ''' A Title URL for a title to be queried.
    ''' </param>
    ''' <param name="additionalTitleRefs">
    ''' A paramarray Strings() containing Title URLs for titles to be queried.</param>
    ''' <returns>
    ''' An object instance of the RestCall class containing a request for one or more title Ratings, to be submitted to the Netflix API service in a subsequent query.
    ''' </returns>
    ''' <remarks>
    ''' There are other ways to accomodate the possibility of queries for a single Title vs. multiple Title queries - this is one approach, but an overloaded Function could be another.
    ''' </remarks>
    Public Function GetUsersRatingsTitle(ByVal accessToken As IAccessToken, ByVal titleRef As String, ByVal ParamArray additionalTitleRefs As String()) As RestCall
        Dim result = BaseUrl & "users/" & accessToken.UserId & "/ratings/title?title_refs=" & UrlEncode(titleRef)
        ' TODO: Does this loop need guard code in case the additionalTitleRefs array is empty or null?
        For Each item In additionalTitleRefs
            result &= UrlEncode("," & item)
        Next
        Return New RestCall(RestVerb.Get, result)
    End Function

    Public Function GetUsersRatingsActual(ByVal accessToken As IAccessToken, ByVal titleRef As String, ByVal ParamArray additionalTitleRefs As String()) As RestCall
        Dim result = BaseUrl & "users/" & accessToken.UserId & "/ratings/title/actual?title_refs=" & UrlEncode(titleRef)
        For Each item In additionalTitleRefs
            result &= UrlEncode("," & item)
        Next
        Return New RestCall(RestVerb.Get, result)
    End Function

    Public Function PostUsersRatingsActual(ByVal accessToken As IAccessToken, ByVal titleRef As String, ByVal rating As Rating) As RestCall
        Return New RestCall(RestVerb.Post, (BaseUrl & "users/" & accessToken.UserId & "/ratings/title/actual?title_ref=" & UrlEncode(titleRef)).AddTerm("rating", rating.RatingString))
    End Function

    Public Function PutUsersRatingsActual(ByVal ratingId As String, ByVal rating As Rating) As RestCall
        Return New RestCall(RestVerb.Get, (ratingId & "?").AddTerm("method", "PUT").AddTerm("rating", rating.RatingString))
    End Function

    Public Function GetUsersRatingsPredicted(ByVal accessToken As IAccessToken, ByVal titleRef As String, ByVal ParamArray additionalTitleRefs As String()) As RestCall
        Dim result = BaseUrl & "users/" & accessToken.UserId & "/ratings/title/predicted?title_refs=" & UrlEncode(titleRef)
        For Each item In additionalTitleRefs
            result &= "," & UrlEncode(item)
        Next
        Return New RestCall(RestVerb.Get, result)
    End Function

    'Public Function GetUsersQueuesInstantAvailable(byval accessToken as IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?, ByVal updatedMin As DateTimeOffset?, ByVal titleRef As String, ByVal ParamArray additionalTitleRefs As String()) As RestCall
    '	Dim result = BaseUrl & "users/" & accessToken.userid & "/reviews?title_refs=" & UrlEncode(titleRef)
    '	For Each item In additionalTitleRefs
    '		result &= "," & UrlEncode(item)
    '	Next

    '	Return New RestCall(RestVerb.Get, result.AddTerm("start_index", startIndex).AddTerm("max_results", maxResults).AddTerm("updated_min", updatedMin))
    'End Function

    Public Function GetUsersQueuesInstantAvailable(ByVal accessToken As IAccessToken, ByVal startIndex As Integer?, ByVal maxResults As Integer?) As RestCall
        Return New RestCall(RestVerb.Get, BaseUrl & "users/" & accessToken.UserId & "/recommendations?".AddTerm("start_index", startIndex).AddTerm("max_results", maxResults))
    End Function

    <Extension()> Private Function AddTerm(ByVal base As String, ByVal term As String, ByVal value As DateTime?) As String
        'TODO - Once vetted, this function should be moved to ClrExtensions
        'RFC 3339 format
        'http://www.ietf.org/rfc/rfc3339.txt

        If value.HasValue Then
            Dim universalTime = value.Value.ToUniversalTime
            Return AddTerm(base, term, universalTime.ToString("yyyy-MM-ddTHH:mm:ss"))
        Else
            Return base
        End If

    End Function

#If Full35 Then

    <Extension()> Private Function AddTerm(ByVal base As String, ByVal term As String, ByVal value As DateTimeOffset?) As String
        'TODO - Once vetted, this function should be moved to ClrExtensions
        'RFC 3339 format
        'http://www.ietf.org/rfc/rfc3339.txt

        If value.HasValue Then
            Dim universalTime = value.Value.ToUniversalTime
            Return AddTerm(base, term, universalTime.ToString("yyyy-MM-ddTHH:mm:ss"))
        Else
            Return base
        End If

    End Function
#End If

    <Extension()> Private Function AddTerm(ByVal base As String, ByVal term As String, ByVal value As Integer?) As String
        If value.HasValue Then Return AddTerm(base, term, value.Value.ToString) Else Return base
    End Function

    <Extension()> Private Function AddTerm(ByVal base As String, ByVal term As String, ByVal value As String) As String
        If value <> "" Then
            If Not base.Contains("?") Then
                base &= "?" & term & "=" & UrlEncode(value)
            ElseIf base.EndsWith("?") Then
                base &= term & "=" & UrlEncode(value)
            Else
                base &= "&" & term & "=" & UrlEncode(value)
            End If
        End If
        Return base
    End Function


End Module
